-- 导弹控制脚本
peripheral.find("modem", rednet.open)
local properties, system
local protocol, request_protocol = "CBCNetWork", "CBCcenter"
local selfId = os.getComputerID()

system = {
    fileName = "missile_dat", 
    file = nil,      
}
system.init = function()
    system.file = io.open(system.fileName, "r")
    if system.file then
        local tmpProp = textutils.unserialise(system.file:read("a"))
        properties = system.reset()
        for k, v in pairs(properties) do
            if tmpProp[k] then
                properties[k] = tmpProp[k]
            end
        end
        system.file:close()
    else
        properties = system.reset()
        system.updatePersistentData()
    end
end

system.reset = function()
    return {
        missileName = "MISSILE",           -- 导弹名称
        controlCenterId = "-1",            -- 控制中心ID
        password = "123456",               -- 密码
        maxSpeed = 100,                    -- 最大速度
        acceleration = 15,                  -- 加速度
        rotationSpeed = 30,                 -- 旋转速度系数
        initialBoostTime = 5,              -- 初始推进时间(秒)
        initialBoostPower = 30,            -- 初始推进力度
        detonationDistance = 5  -- 默认引爆距离（米）
    }
end

system.updatePersistentData = function()
    system.write(system.fileName, properties)
end

system.write = function(file, obj)
    system.file = io.open(file, "w")
    system.file:write(textutils.serialise(obj))
    system.file:close()
end

system.init()

local missile = peripheral.find("EngineController")
local controlCenter = { 
    tgPos = { x = 0, y = 0, z = 0 }, 
    velocity = { x = 0, y = 0, z = 0 }, 
    fire = false 
}
local getMissilePos = function()
    local pos = missile.getPosition()  
    return { x = pos.x, y = pos.y, z = pos.z }
end

missile.setIdle(false)
local ct = 20
local isActive = false  
local move = false      
local boostStartTime = 0 
local initialFaceVector = {x=0, y=0, z=1} 
local maxmass
local themass

local function mass()
    while true do
        maxmass = missile.getMassCanDrive()
        themass = missile.getMass()
        term.setCursorPos(30, 3)
        printError(string.format("mass: %.2f%%", themass / maxmass * 100))
        sleep(0.5)
    end
end

local genStr = function(s, count)
    local result = ""
    for i = 1, count, 1 do
        result = result .. s
    end
    return result
end

local function vec3Normalize(v)
    local len = math.sqrt(v.x^2 + v.y^2 + v.z^2)
    if len > 0 then
        return {x = v.x/len, y = v.y/len, z = v.z/len}
    end
    return {x = 0, y = 0, z = 0}
end

local function vec3Cross(a, b)
    return {
        x = a.y * b.z - a.z * b.y,
        y = a.z * b.x - a.x * b.z,
        z = a.x * b.y - a.y * b.x
    }
end

local function vec3Dot(a, b)
    return a.x * b.x + a.y * b.y + a.z * b.z
end

local function steeringControl()
    while true do
        if isActive and move and controlCenter.tgPos then
            local position = missile.getPosition()
            local currentFaceVector = missile.getFaceVector()
            local toTarget = {
                x = controlCenter.tgPos.x - position.x,
                y = controlCenter.tgPos.y - position.y,
                z = controlCenter.tgPos.z - position.z
            }
            toTarget = vec3Normalize(toTarget)
            local rotationAxis = vec3Cross(currentFaceVector, toTarget)
            rotationAxis = vec3Normalize(rotationAxis)
            local angle = math.acos(math.min(1, math.max(-1, vec3Dot(currentFaceVector, toTarget))))
            local omega = {
                x = rotationAxis.x * angle * properties.rotationSpeed * themass,
                y = rotationAxis.y * angle * properties.rotationSpeed * themass,
                z = rotationAxis.z * angle * properties.rotationSpeed * themass
            }
            missile.applyInvariantTorque(omega.x, omega.y, omega.z)
        end
        sleep(0.05)
    end
end

local function initialBoost()
    if isActive and not move then
        missile.setIdle(true)
        boostStartTime = os.clock()
        initialFaceVector = missile.getFaceVector() 
        while os.clock() - boostStartTime < properties.initialBoostTime do
            local force = {
                x = initialFaceVector.x * themass * properties.initialBoostPower,
                y = initialFaceVector.y * themass * properties.initialBoostPower,
                z = initialFaceVector.z * themass * properties.initialBoostPower
            }
            missile.applyInvariantForce(force.x, force.y, force.z)
            sleep(0.05)
        end
        move = true 
    end
end
local function triggerDetonation()
    redstone.setOutput("front", true) 
    isActive = false
    move = false
    print("Detonated at target!")
end
local function movementControl()
    while true do
        initialBoost()  
        if isActive and move and controlCenter.tgPos then
            local position = missile.getPosition()
            local velocity = missile.getVelocity()
            local toTarget = {
                x = controlCenter.tgPos.x - position.x,
                y = controlCenter.tgPos.y - position.y,
                z = controlCenter.tgPos.z - position.z
            }
            local distance = math.sqrt(toTarget.x^2 + toTarget.y^2 + toTarget.z^2)
            local desiredSpeed = math.min(properties.maxSpeed, distance * properties.acceleration)
            local desiredVelocity = {
                x = toTarget.x / distance * desiredSpeed,
                y = toTarget.y / distance * desiredSpeed,
                z = toTarget.z / distance * desiredSpeed
            }
            local deltaV = {
                x = desiredVelocity.x - velocity.x,
                y = desiredVelocity.y - velocity.y,
                z = desiredVelocity.z - velocity.z
            }
            local force = {
                x = deltaV.x * themass,
                y = deltaV.y * themass,
                z = deltaV.z * themass
            }
            missile.applyInvariantForce(force.x, force.y, force.z)
        end
        sleep(0.05)
    end
end

local function checkDetonationDistance()
    while true do
        if isActive and controlCenter.tgPos then
            local missilePos = getMissilePos()
            local targetPos = controlCenter.tgPos
            local dx = targetPos.x - missilePos.x
            local dy = targetPos.y - missilePos.y
            local dz = targetPos.z - missilePos.z
            local distance = math.sqrt(dx*dx + dy*dy + dz*dz)
            if distance <= properties.detonationDistance then
                triggerDetonation()
                break  
            end
        end
        sleep(0.1)  
    end
end

local listener = function()
    local controlCenterId = tonumber(properties.controlCenterId) or 0
    while true do
        local id, msg = rednet.receive(protocol, 2)
        if id == controlCenterId then
            controlCenter = msg
            ct = 20
            if controlCenter.fire and not isActive then
                isActive = true
                move = false
                boostStartTime = 0
            end
        end
    end
end

local sendRequest = function()
    while true do
        local controlCenterId = #properties.controlCenterId == 0 and 0 or tonumber(properties.controlCenterId)
        controlCenterId = controlCenterId and controlCenterId or 0
        
        local position = missile.getPosition()
        local faceVector = missile.getFaceVector()
        
        rednet.send(controlCenterId, {
            name = properties.missileName,
            pw = properties.password,
            bullets_count = 0,  
            cross_point = position,
            facing = faceVector,
            status = isActive and (move and "moving" or "boosting") or "idle"
        }, request_protocol)
        sleep(0.05)
    end
end

local runControl = function()
    while true do
        if (ct > 0 and controlCenter.fire) then
            isActive = true
        end
        sleep(0.1)
    end
end
---------
local termUtil = {
    cpX = 1,
    cpY = 1,
}
local absTextField = {
    x = 1,
    y = 1,
    len = 15,
    text = "",
    textCorlor = "0",
    backgroundColor = "8",
}

function absTextField:paint()
    local str = ""
    for i = 1, self.len, 1 do
        local text = tostring(self.key[self.value])
        local tmp = string.sub(text, i, i)
        if #tmp > 0 then
            str = str .. tmp
        else
            local tmp2 = ""
            for j = 0, self.len - i, 1 do
                tmp2 = tmp2 .. " "
            end
            str = str .. tmp2
            break
        end
    end

    term.setCursorPos(self.x, self.y)
    term.blit(str, genStr(self.textCorlor, #str), genStr(self.backgroundColor, #str))
end

function absTextField:inputChar(char)
    local xPos, yPos = term.getCursorPos()
    xPos = xPos + 1 - self.x
    local field = tostring(self.key[self.value])
    if #field < self.len then
        if self.type == "number" then
            if char >= '0' and char <= '9' then
                if field == "0" then
                    field = char
                else
                    field = string.sub(field, 1, xPos) .. char .. string.sub(field, xPos, #field)
                end

                self.key[self.value] = tonumber(field)
                termUtil.cpX = termUtil.cpX + 1
            end
            if char == '-' then
                self.key[self.value] = -self.key[self.value]
            end
        elseif self.type == "string" then
            local strEnd = string.sub(field, xPos, #field)
            field = string.sub(field, 1, xPos - 1) .. char .. strEnd
            self.key[self.value] = field
            termUtil.cpX = termUtil.cpX + 1
        end
    end
end

function absTextField:inputKey(key)
    local xPos, yPos = term.getCursorPos()
    local field = tostring(self.key[self.value])
    local minXp = self.x
    local maxXp = minXp + #field
    if key == 259 or key == 261 then 
        if xPos > minXp then
            termUtil.cpX = termUtil.cpX - 1
            if #field > 0 and termUtil.cpX > 1 then
                local index = termUtil.cpX - self.x
                field = string.sub(field, 1, index) .. string.sub(field, index + 2, #field)
            end
            if self.type == "number" then
                local number = tonumber(field)
                if not number then
                    self.key[self.value] = 0
                else
                    self.key[self.value] = number
                end
            elseif self.type == "string" then
                self.key[self.value] = field
            end
        end
    elseif key == 257 or key == 335 then
    elseif key == 262 or key == 263 then
        if key == 262 then
            termUtil.cpX = termUtil.cpX + 1
        elseif key == 263 then
            termUtil.cpX = termUtil.cpX - 1
        end
    elseif key == 264 or key == 258 then
    elseif key == 265 then
    end
    termUtil.cpX = termUtil.cpX > maxXp and maxXp or termUtil.cpX
    termUtil.cpX = termUtil.cpX < minXp and minXp or termUtil.cpX
end


function absTextField:click(x, y)
    local xPos = self.x
    if x >= xPos then
        if x < xPos + #tostring(self.key[self.value]) then
            termUtil.cpX, termUtil.cpY = x, y
        else
            termUtil.cpX, termUtil.cpY = xPos + #tostring(self.key[self.value]), y
        end
    end
end

local newTextField = function(key, value, x, y)
    return setmetatable({ key = key, value = value, type = type(key[value]), x = x, y = y },
        { __index = absTextField })
end

local absSelectBox = {
    x = 1,
    y = 1,
    label = "",
    contents = {},
    count = 0,
    interval = 0,
    fontColor = "8",
    backgroundColor = "f",
    selectColor = "e"
}

function absSelectBox:paint()
    term.setCursorPos(self.x, self.y)
    local select = tostring(self.key[self.value])
    for i = 1, #self.contents, 1 do
        local str = tostring(self.contents[i])
        if select == str then
            term.blit(str, genStr(self.backgroundColor, #str), genStr(self.selectColor, #str))
        else
            term.blit(str, genStr(self.fontColor, #str), genStr(self.backgroundColor, #str))
        end
        for j = 1, self.interval, 1 do
            term.write(" ")
        end
    end
end

function absSelectBox:click(x, y)
    local xPos = x + 1 - self.x
    local index = 0
    for i = 1, #self.contents, 1 do
        if xPos >= index and xPos <= index + #tostring(self.contents[i]) then
            self.key[self.value] = self.contents[i]
            break
        end
        index = index + #tostring(self.contents[i]) + self.interval
    end
end

local newSelectBox = function(key, value, x, y, contents, interval)
    local sb = setmetatable({ key = key, value = value, x = x, y = y, contents = contents or {}, interval = interval or 0 },
        { __index = absSelectBox })
    sb.count = #sb.contents
    return sb
end

local absSlider = {
    x = 1,
    y = 1,
    min = 0,
    max = 1,
    len = 20,
    fontColor = "8",
    backgroundColor = "f"
}

function absSlider:paint()
    local field = self.key[self.value]
    if field == "-" then
        field = 0
    end
    local maxVal = self.max - self.min
    local xPos = math.floor((field - self.min) * (self.len / maxVal) + 0.5)
    xPos = xPos < 1 and 1 or xPos
    term.setCursorPos(self.x, self.y)
    for i = 1, self.len, 1 do
        if xPos == i then
            term.blit(" ", self.backgroundColor, self.fontColor)
        else
            term.blit("-", self.fontColor, self.backgroundColor)
        end
    end
end

function absSlider:click(x, y)
    local xPos = x + 1 - self.x
    if xPos > self.len then
        xPos = self.len
    end
    self.key[self.value] = math.floor((self.max - self.min) * (xPos / self.len) + 0.5) + self.min
end

local runTerm = function()
    -- 定义UI元素
    local fieldTb = {
        missileName = newTextField(properties, "missileName", 20, 2),
        controlCenterId = newTextField(properties, "controlCenterId", 20, 3),
        password = newTextField(properties, "password", 20, 4),
        maxSpeed = newTextField(properties, "maxSpeed", 20, 5),
        acceleration = newTextField(properties, "acceleration", 20, 6),
        rotationSpeed = newTextField(properties, "rotationSpeed", 20, 7),
        initialBoostTime = newTextField(properties, "initialBoostTime", 20, 8),
        initialBoostPower = newTextField(properties, "initialBoostPower", 20, 9),
        detonationDistance = newTextField(properties, "detonationDistance", 20, 11)
    }
    
    -- 设置字段长度
    fieldTb.missileName.len = 20
    fieldTb.controlCenterId.len = 5
    fieldTb.password.len = 14
    fieldTb.maxSpeed.len = 5
    fieldTb.acceleration.len = 5
    fieldTb.rotationSpeed.len = 5
    fieldTb.initialBoostTime.len = 5
    fieldTb.initialBoostPower.len = 5
    fieldTb.detonationDistance.len = 2
    -- 初始化UI
    local alarm_flag = false
    term.clear()
    term.setCursorPos(15, 8)
    term.write("Press any key to continue")
    while true do
        local eventData = { os.pullEvent() }
        local event = eventData[1]
        if event == "mouse_up" or event == "key_up" or event == "alarm" or
           event == "mouse_click" or event == "mouse_drag" or event == "key" or event == "char" then
            if not alarm_flag then
                alarm_flag = true
                term.clear()
                term.setCursorPos(15, 1)
                print("=== Missile Configuration ===")
                term.setCursorPos(1, 2) print("Missile Name:")
                term.setCursorPos(1, 3) print("Control Center ID:")
                term.setCursorPos(1, 4) print("Password:")
                term.setCursorPos(1, 5) print("Max Speed:")
                term.setCursorPos(1, 6) print("Acceleration:")
                term.setCursorPos(1, 7) print("Rotation Speed:")
                term.setCursorPos(1, 8) print("Boost Time:")
                term.setCursorPos(1, 9) print("Boost Power:")
                term.setCursorPos(1, 11)print("Detonation Distance:")
            end
            
            -- 绘制所有字段
            for k, v in pairs(fieldTb) do
                v:paint()
            end
            
            -- 处理输入事件
            if event == "mouse_click" then
                term.setCursorBlink(true)
                local x, y = eventData[3], eventData[4]
                for k, v in pairs(fieldTb) do
                    if y == v.y and x >= v.x and x <= v.x + v.len then
                        v:click(x, y)
                        break
                    end
                end
            elseif event == "key" then
                local key = eventData[2]
                for k, v in pairs(fieldTb) do
                    if termUtil.cpY == v.y and termUtil.cpX >= v.x and termUtil.cpX <= v.x + v.len then
                        v:inputKey(key)
                        break
                    end
                end
            elseif event == "char" then
                local char = eventData[2]
                for k, v in pairs(fieldTb) do
                    if termUtil.cpY == v.y and termUtil.cpX >= v.x and termUtil.cpX <= v.x + v.len then
                        v:inputChar(char)
                        break
                    end
                end
            end
            system.updatePersistentData()
        end
    end
end
---------------------------
parallel.waitForAll(mass,runControl,listener,sendRequest,steeringControl,movementControl,runTerm,checkDetonationDistance)